今明兩天要介紹的是 React 中的程式碼拆分(Code Splitting)與動態匯入(Dynamic Import),會先從一般的靜態匯入開始介紹,說明靜態匯入可能產生的問題,再接續介紹程式碼拆分與動態匯入。雖然之前在 Module 模式已經有介紹過靜態與動態匯入,不過那時是著重於純 JavaScript 的匯入,這兩天會著重介紹在 React 中的程式碼拆分與動態匯入。
Module 模式的文章內曾提過,使用 import
關鍵字可匯入另一個模組 export
匯出的模組,在 React 中我們通常會先在一個檔案內定義一個元件,並在要使用的地方匯入該元件做使用,例如以下:
// Text.js
// 在 Text.js 檔案定義 Text 元件並匯出
const Text = ({ color = 'black', children }) => {
return <p style={{ color }}>{children}</p>;
};
export default Text
// App.js
// 在 App.js 檔案匯入 Text 元件做使用
import Text from './Text.js' // 當 JS 引擎執行到匯入模組的這行程式碼時,就會執行匯入
const App = () => {
return (
<>
<Text color="blue">Hello!🌼</Text>
<Text color="green">HiHi🙌</Text>
</>
);
}
export default App;
當我們使用 import Text from './Text.js'
的時候,就屬於靜態匯入,在預設情況下,靜態匯入的所有模組都會被加到初始 bundle 中。在打包 React 應用程式程式碼時,打包工具(如 webpack)會將Text
元件的程式碼和 App
元件的程式碼一起打包到一個初始 bundle 內。
靜態匯入是開發 React 時最常見的匯入方式,優點包括:
靜態匯入看似運作順利,但可能有些潛在問題。隨著應用程式越來越大,我們打包的 bundle 檔案就會變得越來越肥大,就會影響應用程式的載入時間。
假設我們的 App.js
匯入了多種元件,這些元件都會被一起打包到最終的 bundle 檔案內,且每個子元件都可能有自己的樣式和功能。如果這些元件的程式碼量很大,或者它們包含大量的圖片或其他資源,最後打包的 bundle 檔案大小就會太大,而導致應用程式載入時間過長,影響應用的使用者體驗。
// App.js
import Header from './Header';
import Footer from './Footer';
import Sidebar from './Sidebar';
import Content from './Content';
import Navbar from './Navbar';
import Modal from './Modal';
import ToolTip from './ToolTip';
import Dashboard from './Dashboard';
import Settings from './Settings';
const App = () => {
return (
<div>
<Header />
<Navbar />
<div className="main-content">
<Sidebar />
<Content />
</div>
<Modal />
<ToolTip />
<Dashboard />
<Settings />
<Footer />
</div>
);
};
export default App;
為何會影響應用的使用者體驗?因為在 App.js
渲染畫面之前,必須先載入並剖析所有模組,完成所有步驟後使用者才能看到畫面、和應用程式互動。瀏覽器處理 JavaScript 和渲染畫面的完整步驟如下:
示意圖如下:
圖 1 瀏覽器處理 JavaScript 流程示意(資料來源:自行繪製)
當我們的 main.bundle.js
(也就是打包的最終 bundle)容量太大,載入和解析時間就會更長,就需要等待更久的時間才能渲染 App.js
的畫面,使用者會看到更久的白屏畫面,而影響了使用者體驗。
那要如何讓使用者更快看到畫面呢? 就要減少 bundle 大小來加快上述步驟執行的時間(載入、處理和執行時間),我們可以不要一次載入一個肥大的 bundle,而是將 bundle 拆分為多個,只在使用者互動、或頁面需要時才動態匯入模組,如此可減少初始 bundle 的大小。
示意圖如下,我們將原先單一一個巨大的 bundle 拆分為多個小 bundle,在需要時再載入小 bundle。
圖 2 拆分 bundle(資料來源:自行繪製)
減少 main.bundle.js
大小,可讓使用者更快看到初始畫面。Before 和 After 示意圖如下。
圖 3 程式碼拆分 Before 與 After(資料來源:自行繪製)
而這種「將單一一個 bundle 拆分為多個小 bundle」的概念就稱為 Code Splitting,Code Splitting 就是為了解決單一 bundle 檔案過大的問題,將單一的 bundle 切分為數個小 bundle,可以平行載入、也可以在有需要時才載入特定 bundle,也可對不常變動的 bundle 快取。雖然透過 code splitting 沒辦法減少應用程式中整體程式碼檔案大小(只是將單一一個 bundle 拆分成多個小的,所有小 bundle 加起來,檔案大小也會跟單一 bundle 差不多),但可以避免使用者在一次載入內花太多時間和流量下載那些用不到的檔案。Code Splitting 主要是改善使用者第一次載入應用所需的時間,來達到載入效能的優化。
示意圖中有提及 FCP、LCP 和 TTI,可以看出 Code Splitting 後能降低這 3 個指標度量的時間,以下簡要介紹 Code Splitting 帶來的好處以及指標的定義:
(關於更多衡量網頁效能的指標,之後文章會再做介紹)
了解 Code Splitting 解決的問題與帶來的好處後,就要來看看如何實現 Code Splitting。Code Splitting 可分為兩種常見技巧,一是抽離第三方套件,二是動態匯入模組/元件。明天將會聚焦介紹動態匯入的方法,對抽離第三方套件的實作技巧有興趣的推薦閱讀這篇文章。